﻿using System;
using System.Text;
using System.Threading;
using System.Runtime.InteropServices;

namespace Common
{
    [StructLayout(LayoutKind.Sequential)]
    public struct DefaultMessage
    {
        public static int MySize { get { return Marshal.SizeOf(typeof(DefaultMessage)); } }

        public int Recog;
        public ushort Identity;
        public ushort Param;
        public ushort Tag;
        public ushort Series;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct MsgHeader
    {
        public const uint Serial = 0xAA55AA55;

        public static int MySize { get { return Marshal.SizeOf(typeof(MsgHeader)); } }

        public uint Code;
        public uint SNumber;
        public ushort UserGateIndex;
        public ushort Ident;
        public ushort UserListIndex;
        public ushort Temp;
        public int Length;
    }



    public static class EnDecode
    {
        public const int DefBlockSize = 16;
        public const int MaxBufferSize = 1024 * 16;

        private static byte[] Decode6BitMask = { 0xFC, 0xF8, 0xF0, 0xE0, 0xC0 };

        private static byte[] EncBuf = new byte[MaxBufferSize];
        private static byte[] TempBuf = new byte[MaxBufferSize];

        private static IntPtr EnDecodePtr = Marshal.AllocHGlobal(MaxBufferSize);
        private static object SyncRoot = new object();


        public static void MakeDefMsg(ref DefaultMessage defMsg, ushort ident, int recog/* = 0*/, ushort param/* = 0*/, ushort tag/* = 0*/, ushort series/* = 0*/)
        {
            defMsg.Identity = ident;
            defMsg.Recog = recog;
            defMsg.Param = param;
            defMsg.Tag = tag;
            defMsg.Series = series;
        }

        #region Encode6BitBuf + Decode6BitBuf
        private static int BufferLen(byte[] buffer)
        {
            int count = 0;

            if (buffer != null)
                while (buffer[count] != 0)
                    count++;

            return count;
        }

        private static int Encode6BitBuf(byte[] src, byte[] dest, int srcLen, int destLen)
        {
            int destPos = 0;
            int restCount = 0;
            byte made = 0, rest = 0;
            byte ch1 = 0;

            for (int i = 0; i < srcLen; i++)
            {
                if (destPos >= destLen) break;
                ch1 = src[i];

                made = (byte)((rest | (ch1 >> (2 + restCount))) & 0x3F);
                rest = (byte)(((ch1 << (8 - (2 + restCount))) >> 2) & 0x3F);

                restCount += 2;

                if (restCount < 6)
                {
                    dest[destPos++] = (byte)(made + 0x3C);
                }
                else
                {
                    if (destPos < destLen - 1)
                    {
                        dest[destPos++] = (byte)(made + 0x3C);
                        dest[destPos++] = (byte)(rest + 0x3C);
                    }
                    else
                        dest[destPos++] = (byte)(made + 0x3C);

                    restCount = 0;
                    rest = 0;
                }
            }

            if (restCount > 0)
            {
                dest[destPos] = (byte)(rest + 0x3C);
                destPos++;
            }

            dest[destPos] = 0;

            return destPos;
        }

        private static int Decode6BitBuf(byte[] src, byte[] dest, int srcLen, int destLen)
        {
            //int srcLen = BufferLen(src);
            int destPos = 0, bitPos = 2;
            int madeBit = 0;
            byte ch, chCode;
            byte tmp = 0;

            for (int i = 0; i < srcLen; i++)
            {
                if (((src[i] - 0x3C) >= 0) && ((src[i] - 0x3C) <= 64))
                    ch = (byte)(src[i] - 0x3C);
                else
                {
                    destPos = 0;
                    break;
                }

                if (destPos >= destLen) break;

                if ((madeBit + 6) >= 8)
                {
                    chCode = (byte)(tmp | ((ch & 0x3F) >> (6 - bitPos)));

                    dest[destPos++] = chCode;
                    madeBit = 0;

                    if (bitPos < 6)
                        bitPos += 2;
                    else
                    {
                        bitPos = 2;
                        continue;
                    }
                }

                tmp = (byte)((ch << bitPos) & Decode6BitMask[bitPos - 2]);

                madeBit += (8 - bitPos);
            }

            dest[destPos] = 0;

            return destPos;
        }
        #endregion

        #region Decode routines
        public static void DecodeMessage(string str, out DefaultMessage defMsg)
        {
            lock (SyncRoot)
            {
                int writecount = Encoding.ASCII.GetBytes(str, 0, str.Length, TempBuf, 0);
                if (writecount > 0)
                {
                    int nPos = Decode6BitBuf(TempBuf, EncBuf, writecount, 1024);
                    Marshal.Copy(EncBuf, 0, EnDecodePtr, nPos);
                    defMsg = (DefaultMessage)Marshal.PtrToStructure(EnDecodePtr, typeof(DefaultMessage));
                }
                else
                    defMsg = new DefaultMessage();
            }
        }

        public static string DecodeString(string str)
        {
            string retstr = string.Empty;

            lock (SyncRoot)
            {
                int writecount = Encoding.ASCII.GetBytes(str, 0, str.Length, TempBuf, 0);
                if (writecount > 0)
                {
                    int nPos = Decode6BitBuf(TempBuf, EncBuf, writecount, MaxBufferSize);
                    retstr = Encoding.ASCII.GetString(EncBuf, 0, nPos);
                }
            }

            return retstr;
        }

        /*public static object DecodeObject(string str, Type structType)
        {
            lock (SyncRoot)
            {
                int nPos = Decode6BitBuf(Encoding.ASCII.GetBytes(str), ref EncBuf, MaxBufferSize);
                Marshal.Copy(EncBuf, 0, EnDecodePtr, nPos);
                return Marshal.PtrToStructure(EnDecodePtr, structType);
            }
        }*/

        public static void DecodeObject<T>(string str, out T anything)
        {
            lock (SyncRoot)
            {
                int writecount =Encoding.ASCII.GetBytes(str, 0, str.Length, TempBuf, 0);
                if (writecount > 0)
                {
                    int nPos = Decode6BitBuf(TempBuf, EncBuf, writecount, MaxBufferSize);
                    Marshal.Copy(EncBuf, 0, EnDecodePtr, nPos);
                    anything = (T)Marshal.PtrToStructure(EnDecodePtr, typeof(T));
                }
                else
                    anything = default(T);
            }
        }

        public static void DecodeBuffer<T>(byte[] src, out T anything)
        {
            lock (SyncRoot)
            {
                int nPos = Decode6BitBuf(src, EncBuf, src.Length, MaxBufferSize);
                Marshal.Copy(EncBuf, 0, EnDecodePtr, nPos);
                anything = (T)Marshal.PtrToStructure(EnDecodePtr, typeof(T));
            }
        }
        #endregion

        #region Encode routines
        public static string EncodeMessage(DefaultMessage defMsg)
        {
            lock (SyncRoot)
            {
                Marshal.StructureToPtr(defMsg, EnDecodePtr, true);
                Marshal.Copy(EnDecodePtr, TempBuf, 0, DefaultMessage.MySize);

                int nPos = Encode6BitBuf(TempBuf, EncBuf, DefaultMessage.MySize, 1024);
                return Encoding.ASCII.GetString(EncBuf, 0, nPos);
            }
        }

        public static string EncodeString(string str)
        {
            string retstr = string.Empty;

            lock (SyncRoot)
            {
                int writecount = Encoding.ASCII.GetBytes(str, 0, str.Length, TempBuf, 0);
                if (writecount > 0)
                {
                    int nPos = Encode6BitBuf(TempBuf, EncBuf, writecount, MaxBufferSize);
                    retstr = Encoding.ASCII.GetString(EncBuf, 0, nPos);
                }
            }

            return retstr;
        }

        public static string EncodeObject<T>(T anything)
        {
            lock (SyncRoot)
            {
                int sz = Marshal.SizeOf(anything);
                if (sz >= MaxBufferSize)
                    return string.Empty;

                Marshal.StructureToPtr(anything, EnDecodePtr, true);
                Marshal.Copy(EnDecodePtr, TempBuf, 0, sz);

                int nPos = Encode6BitBuf(TempBuf, EncBuf, sz, MaxBufferSize);
                return Encoding.ASCII.GetString(EncBuf, 0, nPos);
            }
        }

        /*public static byte[] EncodeBuffer(object anything)
        {
            lock (SyncRoot)
            {
                int sz = Marshal.SizeOf(anything);
                if (sz >= MaxBufferSize)
                    return null;

                Marshal.StructureToPtr(anything, EnDecodePtr, true);
                Marshal.Copy(EnDecodePtr, TempBuf, 0, sz);

                byte[] dest = new byte[MaxBufferSize];
                int nPos = Encode6BitBuf(TempBuf, ref dest, sz, MaxBufferSize);
                dest[nPos] = 0;
                return dest;
            }
        }*/

        public static byte[] EncodeBuffer<T>(T anything)
        {
            lock (SyncRoot)
            {
                int sz = Marshal.SizeOf(anything);
                if (sz >= MaxBufferSize)
                    return null;

                Marshal.StructureToPtr(anything, EnDecodePtr, true);
                Marshal.Copy(EnDecodePtr, TempBuf, 0, sz);

                byte[] dest = new byte[MaxBufferSize];
                int nPos = Encode6BitBuf(TempBuf, dest, sz, MaxBufferSize);
                dest[nPos] = 0;
                return dest;
            }
        }
        #endregion
    }
}
